/*******************************************************************************
 * NES Mapper for BBK
 *
 *  Author:  <fanoble@yeah.net>
 *
 *  Create:   2014-06-24, by fanoble
 *******************************************************************************
 */

#include <assert.h>
#include <stdio.h>

#include "fdc.h"

#include "LPC_D6_SYNTH.H"

#define OPT_ADDR_MAP

//------------------------------------------------------------------------------

int MapperBBK::FeedLPC(void* host, unsigned char* food)
{
	int ret;
	MapperBBK* thiz = (MapperBBK*)host;

	ret = LPC_CMD_NONE;

//	::WaitForSingleObject(thiz->reqLPC, INFINITE);

	while (thiz->nSPReadPos == thiz->nSpWritePos)
	{
		if (!thiz->bRunLPC)
		{
			ret = LPC_CMD_STOP;
			break;
		}
		else if (thiz->nResetLPC)
		{
			ret = LPC_CMD_RESET;
			thiz->nResetLPC = FALSE;
			break;
		}

		Sleep(10);
	}

	if (LPC_CMD_NONE == ret)
	{
		*food = thiz->nSPData[thiz->nSPReadPos];
		thiz->nSPReadPos = (thiz->nSPReadPos + 1) & (SP_BUF_SIZE - 1);
		ret = LPC_CMD_PAYLOAD;
	}
	else
	{
		::SetEvent(thiz->ackLPC);
	}

	return ret;
}

DWORD WINAPI MapperBBK::LPCThreadProc(LPVOID lpParam)
{
	void*    synLPC;
	CWaveOut wavLPC;
	short    pcmBuf[1024]; // should be enough

	MapperBBK* thiz = (MapperBBK*)lpParam;

	wavLPC.Open(10000, 1); // 10Khz Mono

	synLPC = lpc_d6_synth_new(FeedLPC, thiz);

	while (thiz->bRunLPC)
	{
		int eos;
		int pcm_size;

		short* pcm;

		eos = lpc_d6_synth_do(synLPC, pcmBuf, &pcm_size);

		pcm = pcmBuf;
		pcm_size *= sizeof(short);

		while (pcm_size > 1)
		{
			int consumed;

			consumed = wavLPC.WriteNotWait(pcm, pcm_size);
			pcm += consumed / 2;
			pcm_size -= consumed;

			// command response
			if (!thiz->bRunLPC)
			{
				::SetEvent(thiz->ackLPC);
				break;
			}

			if (thiz->nResetLPC)
			{
				thiz->nResetLPC = FALSE;
				lpc_d6_synth_reset(synLPC);
				::SetEvent(thiz->ackLPC);
			}

			if (0 == consumed)
				Sleep(10);
		}
	}

	lpc_d6_synth_delete(synLPC);

	wavLPC.Close();

	return 0;
}

//------------------------------------------------------------------------------

void MapperBBK::Init()
{
	DWORD dwThreadID;

	nSPReadPos = nSpWritePos = 0;

	bRunLPC = TRUE;

	reqLPC = ::CreateEvent(NULL, FALSE, FALSE, NULL);
	ackLPC = ::CreateEvent(NULL, FALSE, FALSE, NULL);

	::ResetEvent(reqLPC);
	::ResetEvent(ackLPC);

	thdLPC = ::CreateThread(NULL, 0, LPCThreadProc, this, 0, &dwThreadID);
}

MapperBBK::~MapperBBK()
{
	bRunLPC = FALSE;

//	::SetEvent(reqLPC);
	::WaitForSingleObject(ackLPC, INFINITE);
	::WaitForSingleObject(thdLPC, INFINITE);

	CloseHandle(reqLPC);
	CloseHandle(ackLPC);
	CloseHandle(thdLPC);
}

//------------------------------------------------------------------------------

void MapperBBK::Reset()
{
	BIOS = nes->rom->GetPROM();

	memset(EDRAM, 0, sizeof(EDRAM));
	memset(EVRAM, 0, sizeof(EVRAM));

	// 4xxx - 7xxx: Fixed Map
	SetPROM_Bank(2, EDRAM + 0x78000, BANKTYPE_RAM); // 8K
	SetPROM_Bank(3, EDRAM + 0x7A000, BANKTYPE_RAM); // 8K

	// 8xxx - Bxxx
	SetPROM_Bank(4, EDRAM + 0x0000, BANKTYPE_RAM); // 8K
	SetPROM_Bank(5, EDRAM + 0x2000, BANKTYPE_RAM); // 8K

	// Cxxx - Fxxx
	SetPROM_Bank(6, BIOS + 0x1C000, BANKTYPE_ROM); // 8K
	SetPROM_Bank(7, BIOS + 0x1E000, BANKTYPE_ROM); // 8K

	SetVROM_Bank( 8, EVRAM + 0x0000, BANKTYPE_VRAM); // 1K
	SetVROM_Bank( 9, EVRAM + 0x0400, BANKTYPE_VRAM); // 1K
	SetVROM_Bank(10, EVRAM + 0x0800, BANKTYPE_VRAM); // 1K
	SetVROM_Bank(11, EVRAM + 0x0C00, BANKTYPE_VRAM); // 1K

//------------------------------------------------------------------------------

	// inno asic
	bMapRam   = 0;
	nRegFF14  = 0;
	nRegFF1C  = 0;
	nRegFF24  = 0;
	nRegFF2C  = 0;
	nRegSPInt = 0;

	bRomSel_89AB = 0;

//------------------------------------------------------------------------------

	// holtek asic
	bSplitMode = 0;

	bEnableIRQ = FALSE;

	nLineCount = 0;

	nNrOfSR = nNrOfVR = 0;

	nQIndex = 0;

	memset(QueueSR, 0, sizeof(QueueSR));
	memset(QueueVR, 0, sizeof(QueueVR));

	nCurScanLine = 0;
	bDiskAccess  = 0;
//------------------------------------------------------------------------------

	nResetLPC = 0;
	nSPReadPos = nSpWritePos = 0;

//------------------------------------------------------------------------------

	// set ExPad
	nes->pad->SetExController(PAD::EXCONTROLLER_BBK_FD1);

//	if (fp_bbk_log) fclose(fp_bbk_log);
//	fp_bbk_log = fopen("bbk_mapper_log.txt", "w");
}

//------------------------------------------------------------------------------

BYTE MapperBBK::ExCmdRead( EXCMDRD cmd )
{
	if (cmd == Mapper::EXCMDRD_DISKACCESS)
		return bDiskAccess;

	return 0;
}

// for debug
LPBYTE MapperBBK::GetExtPMEM( int addr )
{
	return 0;
}

LPBYTE MapperBBK::GetExtVMEM( int addr )
{
	if (addr >= 0x8000)
		return 0;

	return EVRAM + (addr & 0x7FFF);
}

LPBYTE MapperBBK::GetPatternTable( int scanline, int addr )
{
	if (addr >= 0x2000 || scanline >= 240)
		return 0;

	if (bSplitMode)
	{
		int i;
		int start_line;

		start_line = 0;
		for (i = 0; i < nNrOfSR; i++)
		{
			int end_line;

			end_line = start_line + 256 - QueueSR[i];

			if (scanline >= start_line && scanline < end_line)
			{
				// in range
				BYTE page;

				page = QueueVR[i];

				if (addr > 0x1000) page >>= 4;
				page &= 15;
				addr &= 0x0FFF;

				return EVRAM + page * 0x800 + addr;
			}

			start_line = end_line;
		}
	}

	// default
	return PPU_MEM_BANK[addr >> 10] + (addr & 0x3FF);
}

//------------------------------------------------------------------------------

#ifndef OPT_ADDR_MAP
static int mux51(int d0, int s0, int d1, int s1, int d2, int s2, int d3, int d4)
{
	return !( !(d0 & s0) & !(d1 & s1) & !(d2 & s2) & d3 & d4 );
}
#endif

DWORD MapperBBK::MapAddr(WORD addr, int* cs_type, BOOL is_write)
{
	DWORD phy_addr;

#ifdef OPT_ADDR_MAP
	int type;

	phy_addr = 0;
	type = INNO_CS_NONE;

	switch (addr >> 13)
	{
//		case 0: // 01
		case 1: // 23
			if (!(addr & 2) && bMapRam && !bFF01_D4 && is_write)
			{
				type = INNO_CS_DRAM;
				phy_addr = 0x7A000 + (addr & 0x1FFF);
			}
			break;
		case 2: // 45
		case 3: // 67
			type = INNO_CS_DRAM;
			phy_addr = 0x78000 + (addr & 0x3FFF);
			break;
		case 4: // 89
			if (nRegFF14 & 0x40)
			{
				type = INNO_CS_DRAM;
				phy_addr = (nRegFF14 & 0x3F) * 0x2000 + (addr & 0x1FFF);
			}
			else if (!is_write)
			{
				type = INNO_CS_ROM;
				phy_addr = (nRegFF14 & 0xF) * 0x2000 + (addr & 0x1FFF);
			}
			break;
		case 5: // AB
			if (nRegFF14 & 0x40)
			{
				type = INNO_CS_DRAM;
				phy_addr = (nRegFF1C & 0x3F) * 0x2000 + (addr & 0x1FFF);
			}
			else if (!is_write)
			{
				type = INNO_CS_ROM;
				phy_addr = (nRegFF1C & 0xF) * 0x2000 + (addr & 0x1FFF);
			}
			break;
		case 6: // CD
			if (bMapRam)
			{
				type = INNO_CS_DRAM;
				phy_addr = (nRegFF24 & 0x3F) * 0x2000 + (addr & 0x1FFF);
			}
			else if (!is_write)
			{
				type = INNO_CS_ROM;
				phy_addr = 0x1C000 + (addr & 0x1FFF);
			}
			break;
		case 7: // EF
			if (addr < 0xFF00)
			{
				if (bMapRam)
				{
					type = INNO_CS_DRAM;
					phy_addr = (nRegFF2C & 0x3F) * 0x2000 + (addr & 0x1FFF);
				}
				else if (!is_write)
				{
					type = INNO_CS_ROM;
					phy_addr = 0x1E000 + (addr & 0x1FFF);
				}
			}
			// FF00-FFFF
			else if (is_write)
			{
				// IO
				type = INNO_CS_HOLTEK;
				phy_addr = addr & 0xFF;
			}
			// Read
			else if (bMapRam)
			{
				type = INNO_CS_DRAM;
				phy_addr = (nRegFF2C & 0x3F) * 0x2000 + (addr & 0x1FFF);
			}
			// Map to ROM
			else if (0 == (addr & 7))
			{
				// Read IO
				type = INNO_CS_HOLTEK;
				phy_addr = addr & 0xFF;
			}
			else
			{
				type = INNO_CS_ROM;
				phy_addr = 0x1E000 + (addr & 0x1FFF);
			}
			break;
		default:
			
			type = INNO_CS_NONE;
			break;
	}

	if (cs_type) *cs_type = type;

#else
	DWORD _phy_addr = phy_addr;

	// FF14
	int x47 = !!(nRegFF14 & 0x01);
	int x49 = !!(nRegFF14 & 0x02);
	int x51 = !!(nRegFF14 & 0x04);
	int x53 = !!(nRegFF14 & 0x08);
	int x55 = !!(nRegFF14 & 0x10);
	int x57 = !!(nRegFF14 & 0x20);
	int x59 = !!(nRegFF14 & 0x40);

	// FF1C
	int x46 = !!(nRegFF1C & 0x01);
	int x48 = !!(nRegFF1C & 0x02);
	int x50 = !!(nRegFF1C & 0x04);
	int x52 = !!(nRegFF1C & 0x08);
	int x54 = !!(nRegFF1C & 0x10);
	int x56 = !!(nRegFF1C & 0x20);

	// FF24
	int x39 = !!(nRegFF24 & 0x01);
	int x37 = !!(nRegFF24 & 0x02);
	int x35 = !!(nRegFF24 & 0x04);
	int x41 = !!(nRegFF24 & 0x08);
	int x43 = !!(nRegFF24 & 0x10);
	int x45 = !!(nRegFF24 & 0x20);

	// FF2C
	int x38 = !!(nRegFF2C & 0x01);
	int x36 = !!(nRegFF2C & 0x02);
	int x34 = !!(nRegFF2C & 0x04);
	int x40 = !!(nRegFF2C & 0x08);
	int x42 = !!(nRegFF2C & 0x10);
	int x44 = !!(nRegFF2C & 0x20);

	int rw_n = is_write;

	int ff01_d3 = bMapRam;
	int ff01_d4 = bFF01_D4;

//------------------------------------------------------------------------------

	// address decoder
	// A[15] & A[14] & A[13] & A[12] & A[11] & A[10] & A[9] & A[8];
	int reg_ffxx_sel = (addr & 0xFF00) == 0xFF00;

	// reg_ffxx_sel & ~A[2] & ~A[1] & ~A[0];
	int reg_11111111_xxxxx000_sel = (addr & 0xFF07) == 0xFF00;

	// ~ff01_d4 & ~A[15] & ~A[14] & A[13] & ~A[1];
	int reg_0xx_001xxxxx_xxxxxx0x_sel = (!ff01_d4) && ((addr & 0xE002) == 0x2000);

//	int reg_ff10_sel = (A === 16'hff10);
//	int reg_ff18_sel = (A === 16'hff18);
//	int reg_ff04_w = (A === 16'hff04) && rw_n;
//	int reg_ff01_w_n = ~((A === 16'hff01) && rw_n && M2);
//	int reg_ff24_w_n = ~((A === 16'hff24) && rw_n && M2);
//	int reg_ff2c_w_n = ~((A === 16'hff2c) && rw_n && M2);
//	int ff04_or_ff1c_w_n = ~(((A === 16'hff04) || (A === 16'hff1c)) & M2);
//	int ff04_or_ff14_w_n = ~(((A === 16'hff04) || (A === 16'hff14)) & M2);

//------------------------------------------------------------------------------

	int x90_d0 = (0 == (addr & 7)) || is_write;
	int x90_y  = ff01_d3 ? is_write : x90_d0;
	int holtek_cs = reg_ffxx_sel && x90_y;

//------------------------------------------------------------------------------

	int x118_y = !(reg_11111111_xxxxx000_sel ||
					(addr & 0xC000) != 0xC000 || // ~(A[14] & A[15])
					ff01_d3);
	int x98_y = !x59; // xx0_xxxx
	int x104_y = (addr & 0xC000) == 0x8000;// ~A[14] & A[15]; // xxx_10xx
	int x121_y = x98_y && x104_y;
	int x119_y = !(x118_y || x121_y);
	int x120_y = !(x119_y || rw_n);

	int internal_rom_cs = x120_y;

//------------------------------------------------------------------------------

	int x106_y = !(reg_ffxx_sel & rw_n);
	//wire x105_y = ~(A[14] & A[15] & ff01_d3 & x106_y); // x1_11xx
	int x105_y = !(((addr & 0xC000) == 0xC000) && ff01_d3 && x106_y); // x1_11xx

	int x101_y = x104_y ? x98_y : x105_y; // xx0_10xx | ~x1_11xx

	// ~(~A[15] & A[14] & ~A[13] & ~A[12] & ~A[11] & ~A[10])
	int x93_y = (addr & 0xFC00) != 0x4000;

	// A[14] & ~A[15] & x93_y & M2
	int x92_y = ((addr & 0xC000) == 0x4000) && x93_y;

	int x103_y = !x92_y;
	int x100_y = !(x101_y && x103_y);

	int x26_y = ff01_d3 & rw_n;
	int x102_y = reg_0xx_001xxxxx_xxxxxx0x_sel ? x26_y : x100_y;

	// A[14] & A[15] & ~ff01_d3; // x0x_11xx
	int x86_y = ((addr & 0xC000) == 0xC000) && !ff01_d3;

	// ~ff01_d4 & ~A[14] & A[13] & M2;
//	int x32_y = !ff01_d4 && ((addr & 0x6000) == 0x2000);
	// 0xx_x01xxxxx_xxxxxxxx

	// ~A[15] & x32_y & ~A[1];
//	int x33_y = x32_y && ((addr & 0x8002) == 0x0000);
	// 0xx_x01xxxxx_xxxxxxxx
	// xxx_0xxxxxxx_xxxxxx0x
//	int x33_y = reg_0xx_001xxxxx_xxxxxx0x_sel;

	// 0xx_001xxxxx_xxxxxx0x

	// -->
	// ~A[15] & A[14] & ~(~A[15] & A[14] & ~A[13] & ~A[12] & ~A[11] & ~A[10])
	// ~A[15] & A[14] & (A[15] | ~A[14] | A[13] | A[12] | A[11] | A[10])
	// ~A[15] & A[14] & (A[13] | A[12] | A[11] | A[10])
	// xxx_01nnnnxx_xxxxxxxx (nnnn != 0)
	// <--

	int x91_y = reg_0xx_001xxxxx_xxxxxx0x_sel | x92_y; // 0xx_001xxxxx_xxxxxx0x | xxx_01nnnnxx_xxxxxxxx

	// ~A[13] & A[14] & A[15] & ff01_d3; // ff24_sel: x1x_110x
	int x108_y = ((addr & 0xE000) == 0xC000) && ff01_d3;

	//int x112_y = ~(~A[13] & A[14] & A[15]) & A[14] & A[15] & ff01_d3;
	//int x112_y = A[13] & A[14] & A[15] & ff01_d3; // ff2c_sel: x1x_111x
	int x112_y = ((addr & 0xE000) == 0xE000) && ff01_d3;

	int x114_y = !(x112_y & x38); // ff2c_d0
	int x115_y = !(x112_y & x36); // ff2c_d1

	int x116_y = !x59 & x104_y; // rom ff14_sel: xx0_10xx
	int x117_y = x116_y | x86_y | x91_y; // xx0_10xx | x0x_11xx | 0xx_001xxxxx_xxxxxx0x | xxx_01nnnnxx_xxxxxxxx

//	int x123_y = ~A[14] & A[15] & ~A[13]; // xxx_100x
	int x123_y = (addr & 0xE000) == 0x8000;
	int x99_y  = x104_y & x59 & x123_y; // dram ff14_sel: xx1_100x
	int x128_y = x99_y | x116_y; // xx1_100x | xx0_10xx
	int x94_y  = !(x86_y | x91_y); // ~(x0x_11xx | 0xx_001xxxxx_xxxxxx0x | xxx_01nnnnxx_xxxxxxxx)
	int x97_y = !x123_y & x59 & x104_y; // ff1c_sel: xx1_101x

//------------------------------------------------------------------------------

	// DRAM_A13
	int x87_d0 = x47; // ff14_d0
	int x87_s0 = x99_y; // xx1_100x

	int x87_d1 = !!(addr & 0x2000); // A[13]
	int x87_s1 = x117_y; // xx0_10xx | x0x_11xx | 0xx_001xxxxx_xxxxxx0x | xxx_01nnnnxx_xxxxxxxx

	int x87_d2 = x97_y;
	int x87_s2 = x46; // ff1c_d0

	int x87_d3 = !(x39 & x108_y); // ff24_d0

	int x87_d4 = x114_y; // ff2c_d0

	// ROM_A14/DRAM_A14
	int x96_d0 = x49; // ff14_d1
	int x96_s0 = x128_y; // xx1_100x | xx0_10xx

	int x96_d1 = x48; // ff1c_d1
	int x96_s1 = x97_y;

	int x96_d2 = 1;
	int x96_s2 = x86_y; // x0x_11xx

	int x96_d3 = !(x37 & x108_y); // ff24_d1

	int x96_d4 = x115_y; // ff2c_d1

	// ROM_A15/DRAM_A15
	int x85_d0 = x51; // ff14_d2
	int x85_s0 = x128_y;

	int x85_d1 = x34; // ff2c_d2
	int x85_s1 = x112_y;

	int x85_d2 = x97_y;
	int x85_s2 = x50; // ff1c_d2

	int x85_d3 = x94_y; // ~(x0x_11xx | 0xx_001xxxxx_xxxxxx0x | xxx_01nnnnxx_xxxxxxxx)

	int x85_d4 = !(x35 & x108_y); // ff24_d2

	// ROM_A16/DRAM_A16
	int x84_d0 = x53; // ff14_d3
	int x84_s0 = x128_y;

	int x84_d1 = x40; // ff2c_d3
	int x84_s1 = x112_y;

	int x84_d2 = x97_y;
	int x84_s2 = x52; // ff1c_d3

	int x84_d3 = x94_y; // ~(x0x_11xx | 0xx_001xxxxx_xxxxxx0x | xxx_01nnnnxx_xxxxxxxx)

	int x84_d4 = !(x41 & x108_y); // ff24_d3

	// DRAM_A17
	int x83_d0 = x55; // ff14_d4
	int x83_s0 = x128_y;

	int x83_d1 = x42; // ff2c_d4
	int x83_s1 = x112_y;

	int x83_d2 = x97_y;
	int x83_s2 = x54; // ff1c_d4

	int x83_d3 = x94_y; // ~(x0x_11xx | 0xx_001xxxxx_xxxxxx0x | xxx_01nnnnxx_xxxxxxxx)

	int x83_d4 = !(x43 & x108_y); // ff24_d4

	// DRAM_A18
	int x82_d0 = x57; // ff14_d5
	int x82_s0 = x128_y;

	int x82_d1 = x44; // ff2c_d5
	int x82_s1 = x112_y;

	int x82_d2 = x97_y;
	int x82_s2 = x56; // ff1c_d5

	int x82_d3 = x94_y; // ~(x0x_11xx | 0xx_001xxxxx_xxxxxx0x | xxx_01nnnnxx_xxxxxxxx)

	int x82_d4 = !(x45 & x108_y); // ff24_d5

	int x87 = mux51(x87_d0, x87_s0, x87_d1, x87_s1, x87_d2, x87_s2, x87_d3, x87_d4);
	int x96 = mux51(x96_d0, x96_s0, x96_d1, x96_s1, x96_d2, x96_s2, x96_d3, x96_d4);
	int x85 = mux51(x85_d0, x85_s0, x85_d1, x85_s1, x85_d2, x85_s2, x85_d3, x85_d4);
	int x84 = mux51(x84_d0, x84_s0, x84_d1, x84_s1, x84_d2, x84_s2, x84_d3, x84_d4);
	int x83 = mux51(x83_d0, x83_s0, x83_d1, x83_s1, x83_d2, x83_s2, x83_d3, x83_d4);
	int x82 = mux51(x82_d0, x82_s0, x82_d1, x82_s1, x82_d2, x82_s2, x82_d3, x82_d4);

//------------------------------------------------------------------------------

	if (cs_type)
	{
		if (internal_rom_cs)
			*cs_type = INNO_CS_ROM;
		else if (x102_y)
			*cs_type = INNO_CS_DRAM;
		else if (holtek_cs)
			*cs_type = INNO_CS_HOLTEK;
		else
			*cs_type = INNO_CS_NONE;
	}

	if (internal_rom_cs)
	{
		// ROM
		// A16 A15 A14 A13-A0
		// x84 x85 x96 addr
		phy_addr = (x84 << 16) | (x85 << 15) | (x96 << 14) | (addr & 0x3FFF);

		if (_phy_addr != phy_addr)
			_phy_addr = phy_addr;

		assert(phy_addr < 0x20000);
	}
	else if (x102_y)
	{
		phy_addr = (x82 << 18) | (x83 << 17) | (x84 << 16) | (x85 << 15) |
			(x96 << 14) | (x87 << 13) | (addr & 0x1FFF);

		if (_phy_addr != phy_addr)
			_phy_addr = phy_addr;

		assert(phy_addr < 0x80000);
	}
	else
	{
		if ((_phy_addr & 0xFF) != (phy_addr & 0xFF))
			_phy_addr = phy_addr;
	}
#endif

	return phy_addr;
}

//------------------------------------------------------------------------------

// from NES::Read 4100 - 7FFF
BYTE MapperBBK::ReadLow(WORD addr)
{
#ifdef OPT_ADDR_MAP
	if (addr < 0x4400)
		return (addr >> 8); // empty
	else
		return CPU_MEM_BANK[addr >> 13][addr & 0x1FFF];
#else
	int cs_type;
	DWORD phy_addr;
	BYTE data = 0;

	phy_addr = MapAddr(addr, &cs_type, 0);
	
	if (INNO_CS_ROM == cs_type)
	{
		data = BIOS[phy_addr];
	}
	else if (INNO_CS_DRAM == cs_type)
	{
//		assert(dram_cas);
		data = EDRAM[phy_addr];
	}

//	data = CPU_MEM_BANK[addr >> 13][addr & 0x1FFF];
	
	return data;
#endif
}

// from CPU::RD6502 8000 - FFFF
// Quick bank read after return false
BOOL MapperBBK::ReadHigh(WORD addr, LPBYTE pdata)
{
	int cs_type;
	DWORD phy_addr;

	phy_addr = MapAddr(addr, &cs_type, 0);

	if (INNO_CS_ROM == cs_type)
	{
		*pdata = BIOS[phy_addr];
		return TRUE;
	}
	else if (INNO_CS_DRAM == cs_type)
	{
		*pdata = EDRAM[phy_addr];
		return TRUE;
	}

	// FDC
	if (addr >= 0xFF80 && addr <= 0xFFB8) {
		unsigned char nPort;

		nPort = (addr >> 3) & 7;

		*pdata = nes->fdc->Read(nPort);

		bDiskAccess = 1;

		return TRUE;
	}

	bDiskAccess = 0;

	switch (addr)
	{
		case 0xFF50:
			*pdata = 0; // PC Card
			return TRUE;

//------------------------------------------------------------------------------

		case 0xFF18:
			if (((nSpWritePos + 1) & (SP_BUF_SIZE - 1)) == nSPReadPos)
				*pdata = 0x00; // buffer full, busy
			else
				*pdata = 0x8F; // idle
			return TRUE;

//------------------------------------------------------------------------------

		default:
		//	DEBUGOUT("Read %02X\n", addr);
			break;
	}

	return TRUE;
}

// from NES::Write
// 2000 - 3FFF
// 4100 - 7FFF
void MapperBBK::WriteLow(WORD addr, BYTE data)
{
#ifdef OPT_ADDR_MAP
	// 2000-3FFF
	if (addr < 0x4000)
	{
		if ((addr & 2) == 0 && bMapRam && !bFF01_D4)
			CPU_MEM_BANK[3][addr & 0x1FFF] = data; // Map to 6000-7FFF
	}
	else if (addr >= 0x4400)
	{
		CPU_MEM_BANK[addr >> 13][addr & 0x1FFF] = data;
	}
#else
	int cs_type;
	DWORD phy_addr;
	
	phy_addr = MapAddr(addr, &cs_type, 1);
	
	if (INNO_CS_DRAM != cs_type)
		return;
	
	EDRAM[phy_addr] = data;
#endif
}

// from NES::Write 8000 - FFFF
void MapperBBK::Write(WORD addr, BYTE data)
{
	BOOL bPrevSplitMode;
	BOOL bPrevbEnableIRQ;

	int cs_type;
	DWORD phy_addr;

	phy_addr = MapAddr(addr, &cs_type, 1);

	if (INNO_CS_DRAM == cs_type)
	{
		// fix IRQ handler for mine.cmd
		if (phy_addr == 0x7FFFE && data == 0x9B)
			data = 0x96;

		EDRAM[phy_addr] = data;
		return;
	}

	// FDC
	if (addr >= 0xFF80 && addr <= 0xFFB8) {
		unsigned char nPort;

		nPort = (addr >> 3) & 7;

		bDiskAccess = 1;

		nes->fdc->Write(nPort, data);
		return;
	}

	bDiskAccess = 0;

	switch (addr)
	{
		case 0xFF00:	// KebBoardLEDPort
			// D2: !CapsLock
			// D1: !NumLock
			break;

		case 0xFF01:	// VideoCtrlPort
			// D6 : [holtek] bSplitMode : Mode
			// D4 : [ inno ] PPU Reg Shadow
			// D3 : [ inno ] C000-FFFF map to EDRAM/-ROM
			// D2 : [holtek] INT count/-load : Work
			// D[1:0] : [holtek] 6116's AV10 connection
			//          00: AV10
			//          01: AV11
			//          10: 0
			//          11: 1
//			DEBUGOUT("Write VideoCtrlPort %02X -> [%04X]\n", data, addr);
//			DEBUGOUT("Write %02X -> [%04X]\n", data, addr);

			switch (data & 3)
			{
				case 0: SetVRAM_Mirror(VRAM_VMIRROR);  break;
				case 1: SetVRAM_Mirror(VRAM_HMIRROR);  break;
				case 2: SetVRAM_Mirror(VRAM_MIRROR4L); break;
				case 3: SetVRAM_Mirror(VRAM_MIRROR4H); break;
			}

			bPrevSplitMode = bSplitMode;
			bSplitMode = (data & 0x40) ? 1 : 0;

			if (bPrevSplitMode ^ bSplitMode)
			{
				if (bSplitMode)
				{
				//	DEBUGOUT("[%03d] Enable Split\n", nCurScanLine);
//					if (fp_bbk_log) fprintf(fp_bbk_log, "[%03d] Enable Split\n", ppu_scan_line, data, addr);
				}
				else
				{
				//	DEBUGOUT("[%03d] Disable Split\n", nCurScanLine);
//					if (fp_bbk_log) fprintf(fp_bbk_log, "[%03d] Disable Split\n", ppu_scan_line, data, addr);
				}
			}

			bPrevbEnableIRQ = bEnableIRQ;
			bEnableIRQ = (data & 4) ? TRUE : FALSE;
			
			if (bPrevbEnableIRQ ^ bEnableIRQ)
			{
				if (bEnableIRQ)
				{
					//DEBUGOUT("[%03d] Enable IRQ\n", nCurScanLine);
//					if (fp_bbk_log) fprintf(fp_bbk_log, "[%03d] Enable IRQ\n", ppu_scan_line, data, addr);
				}
				else
				{
					//DEBUGOUT("[%03d] Disable IRQ\n", nCurScanLine);
//					if (fp_bbk_log) fprintf(fp_bbk_log, "[%03d] Disable IRQ\n", ppu_scan_line, data, addr);
				}
			}

			CheckIRQ();

			bFF01_D4 = !!(data & 0x10);

			if (data & 8)
			{
				// map C000-FFF to EDRAM, page truncated to 512K
				SetPROM_Bank(6, EDRAM + nRegFF24 * 0x2000, BANKTYPE_RAM);
				SetPROM_Bank(7, EDRAM + nRegFF2C * 0x2000, BANKTYPE_RAM);

				bMapRam = 1;
			}
			else
			{
				// map C000-FFF to ROM
				SetPROM_Bank(6, BIOS + 0x1C000, BANKTYPE_ROM);
				SetPROM_Bank(7, BIOS + 0x1E000, BANKTYPE_ROM);

				bMapRam = 0;
			}
			break;

		case 0xFF02:	// IntCountPortL
			// D[3:0] : counter
//			nLineCount &= 0xF0;
//			nLineCount |= data & 0x0F;
			nLineCount = data;
			break;

		case 0xFF06:	// IntCountPortH
			// D[3:0] : counter
			nLineCount &= 0x0F;
			nLineCount |= (data & 0x0F) << 4;
			break;

//------------------------------------------------------------------------------

		case 0xFF04:	// DRAMPagePort
//			DEBUGOUT("Write %02X -> [%04X]\n", data, addr);

			nRegFF14 = (data << 1) & 0x7F;
			nRegFF1C = ((data << 1) & 0x3F) | 1;

			bRomSel_89AB = !(data & 0x20);

			if (bRomSel_89AB)
			{
				// ROM
				data &= 0x07;	//  128K
				SetPROM_Bank(4, BIOS + data * 0x4000 + 0x0000, BANKTYPE_ROM); // 8000 - 9FFF : 8K
				SetPROM_Bank(5, BIOS + data * 0x4000 + 0x2000, BANKTYPE_ROM); // A000 - BFFF : 8K
			}
			else
			{
				// DRAM
				data &= 0x1F;	//  512K
				SetPROM_Bank(4, EDRAM + data * 0x4000 + 0x0000, BANKTYPE_RAM); // 8000 - 9FFF : 8K
				SetPROM_Bank(5, EDRAM + data * 0x4000 + 0x2000, BANKTYPE_RAM); // A000 - BFFF : 8K
			}
			break;

		case 0xFF14:
//			DEBUGOUT("Write %02X -> [%04X]\n", data, addr);
			data &= 0x3F;	//  512K
			nRegFF14 = data | 0x40;
			bRomSel_89AB = 0;

			SetPROM_Bank(4, EDRAM + data * 0x2000, BANKTYPE_RAM);
			break;
		case 0xFF1C:
//			DEBUGOUT("Write %02X -> [%04X]\n", data, addr);
			nRegFF1C = data & 0x3F;
			bRomSel_89AB = 0;

			SetPROM_Bank(5, EDRAM + nRegFF1C * 0x2000, BANKTYPE_RAM);
			break;
		case 0xFF24:	// DRAMPagePortCD : 6
//			DEBUGOUT("Write %02X -> [%04X]\n", data, addr);
			nRegFF24 = data & 0x3F; 

			if (bMapRam)
				SetPROM_Bank(6, EDRAM + nRegFF24 * 0x2000, BANKTYPE_RAM);
			break;
		case 0xFF2C:	// DRAMPagePortEF : 7
//			DEBUGOUT("Write %02X -> [%04X]\n", data, addr);
			nRegFF2C = data & 0x3F; 
			if (bMapRam)
				SetPROM_Bank(7, EDRAM + nRegFF2C * 0x2000, BANKTYPE_RAM);
			break;

//------------------------------------------------------------------------------

		case 0xFF12: // Init
			if (!bEnableIRQ)
			{
				nNrOfSR = 0;
				nNrOfVR = 0;

				if (bSplitMode)
					nQIndex = 0;
			}
			CheckIRQ();
			break;
		case 0xFF0A: // Scanline
			QueueSR[nNrOfSR] = data;
			if (bEnableIRQ)
				nNrOfSR = 0;
			else
				nNrOfSR++;
			CheckIRQ();
			break;
		case 0xFF1A: // VideoBank
			QueueVR[nNrOfVR] = data;
			if (bEnableIRQ)
				nNrOfVR = 0;
			else
				nNrOfVR++;
			CheckIRQ();
			break;
		case 0xFF22: // Start
			if (bSplitMode)
			{
				BYTE page;

				if (!bEnableIRQ)
				{
//					if (nQIndex == nNrOfSR)
//						nQIndex = 1;
//					else
						nQIndex = 0;//nNrOfSR;

					nLineCount = 0;
				}

				nLineCount = QueueSR[nQIndex];

				page = QueueVR[nQIndex] & 15;	// 2K
				SetVROM_Bank(0, EVRAM + page * 0x0800 + 0x0000, BANKTYPE_CRAM);
				SetVROM_Bank(1, EVRAM + page * 0x0800 + 0x0400, BANKTYPE_CRAM);

				page = (QueueVR[nQIndex] >> 4) & 15;	// 2K
				SetVROM_Bank(2, EVRAM + page * 0x0800 + 0x0000, BANKTYPE_CRAM);
				SetVROM_Bank(3, EVRAM + page * 0x0800 + 0x0400, BANKTYPE_CRAM);

				nQIndex++;
			}

			CheckIRQ();
			break;

		case 0xFF03:	// VideoDataPort0
			data &= 0x0F;	// 2K
			SetVROM_Bank(0, EVRAM + data * 0x0800 + 0x0000, BANKTYPE_CRAM);
			SetVROM_Bank(1, EVRAM + data * 0x0800 + 0x0400, BANKTYPE_CRAM);
			break;

		case 0xFF0B:	// VideoDataPort1
			data &= 0x0F;	// 2K
			SetVROM_Bank(2, EVRAM + data * 0x0800 + 0x0000, BANKTYPE_CRAM);
			SetVROM_Bank(3, EVRAM + data * 0x0800 + 0x0400, BANKTYPE_CRAM);
			break;

		case 0xFF13:	// VideoDataPort2
			data &= 0x0F;	// 2K
			SetVROM_Bank(4, EVRAM + data * 0x0800 + 0x0000, BANKTYPE_CRAM);
			SetVROM_Bank(5, EVRAM + data * 0x0800 + 0x0400, BANKTYPE_CRAM);
			break;

		case 0xFF1B:	// VideoDataPort3
			data &= 0x0F;	// 2K
			SetVROM_Bank(6, EVRAM + data * 0x0800 + 0x0000, BANKTYPE_CRAM);
			SetVROM_Bank(7, EVRAM + data * 0x0800 + 0x0400, BANKTYPE_CRAM);
			break;

		case 0xFF23:
			data &= 0x1F;	// 1K
			SetVROM_Bank(0, EVRAM + data * 0x0400, BANKTYPE_CRAM);
			break;

		case 0xFF2B:
			data &= 0x1F;	// 1K
			SetVROM_Bank(1, EVRAM + data * 0x0400, BANKTYPE_CRAM);
			break;

		case 0xFF33:
			data &= 0x1F;	// 1K
			SetVROM_Bank(2, EVRAM + data * 0x0400, BANKTYPE_CRAM);
			break;

		case 0xFF3B:
			data &= 0x1F;	// 1K
			SetVROM_Bank(3, EVRAM + data * 0x0400, BANKTYPE_CRAM);
			break;

		case 0xFF43:
			data &= 0x1F;	// 1K
			SetVROM_Bank(4, EVRAM + data * 0x0400, BANKTYPE_CRAM);
			break;

		case 0xFF4B:
			data &= 0x1F;	// 1K
			SetVROM_Bank(5, EVRAM + data * 0x0400, BANKTYPE_CRAM);
			break;

		case 0xFF53:
			data &= 0x1F;	// 1K
			SetVROM_Bank(6, EVRAM + data * 0x0400, BANKTYPE_CRAM);
			break;

		case 0xFF5B:
			data &= 0x1F;	// 1K
			SetVROM_Bank(7, EVRAM + data * 0x0400, BANKTYPE_CRAM);
			break;

//------------------------------------------------------------------------------

		case 0xFF10:	// SoundPort0/SpeakInitPort
			if (0 == nRegSPInt && (data & 1))
			{
				nResetLPC = TRUE;
//				::SetEvent(reqLPC);
				::WaitForSingleObject(ackLPC, INFINITE);
			}

			nRegSPInt = data & 1;
			break;
		case 0xFF18:	// SoundPort1/SpeakDataPort

			while (((nSpWritePos + 1) & (SP_BUF_SIZE - 1)) == nSPReadPos)
				Sleep(10); // should not be here

			nSPData[nSpWritePos] = data;
			nSpWritePos = (nSpWritePos + 1) & (SP_BUF_SIZE - 1);

//			::SetEvent(reqLPC);
//			::WaitForSingleObject(ackLPC, INFINITE);
			break;

//------------------------------------------------------------------------------

		case 0xFF40:	// PCDaCtPortO/PCCDataPort
			// D4 : Signal to PC's ERROR
			// D[3:0] : Output PC data
			break;
		case 0xFF48:	// PCCtrlPortI/PCCStatsPortI/PCDataPortIH
			// D[7:4] : PC input data
			// D3 : Signal of PC's pin STB
			break;
		case 0xFF50:	// PCDataPortIL/PCCCtrlPoutO
			// D[3:0] : PC input data
			break;

//------------------------------------------------------------------------------

//		case 0xFF09:
//		case 0xFF17:
//			data = data;
//			break;

		default:
//			DEBUGOUT("Write %02X -> [%04X]\n", data, addr);
			break;
	}
}

//------------------------------------------------------------------------------

void MapperBBK::PPU_Latch(WORD addr)
{
	;
}

void MapperBBK::Clock(INT nCycles)
{
}

BOOL MapperBBK::CheckIRQ()
{
	if (254 == nLineCount && bSplitMode)
	{
		if (nQIndex == nNrOfSR)
		{
			if (bEnableIRQ)
			{
//				DEBUGOUT("IRQ_MAPPER0\n");
//				if (fp_bbk_log) fprintf(fp_bbk_log, "[%03d] Set IRQ0\n", ppu_scan_line);
				nes->cpu->SetIRQ(IRQ_MAPPER);
				return TRUE;
			}
		}
	}

	if (254 == nLineCount && !bSplitMode)
	{
		if (bEnableIRQ)
		{
//			DEBUGOUT("IRQ_MAPPER1\n");
//			if (fp_bbk_log) fprintf(fp_bbk_log, "[%03d] Set IRQ1\n", ppu_scan_line);
			nes->cpu->SetIRQ(IRQ_MAPPER);
			return TRUE;
		}
	}

	nes->cpu->ClrIRQ(IRQ_MAPPER);

	return FALSE;
}

void MapperBBK::HSync(INT nScanline)
{
	nCurScanLine = nScanline;

	// auto start while IRQ pending
	if (0 == nScanline && bSplitMode)
	{
		BYTE page;

		// continue use settings of last frame
		nQIndex = 0;

		nLineCount = QueueSR[nQIndex];

		page = QueueVR[nQIndex] & 15;	// 2K
		SetVROM_Bank(0, EVRAM + page * 0x0800 + 0x0000, BANKTYPE_CRAM);
		SetVROM_Bank(1, EVRAM + page * 0x0800 + 0x0400, BANKTYPE_CRAM);
		
		page = (QueueVR[nQIndex] >> 4) & 15;	// 2K
		SetVROM_Bank(2, EVRAM + page * 0x0800 + 0x0000, BANKTYPE_CRAM);
		SetVROM_Bank(3, EVRAM + page * 0x0800 + 0x0400, BANKTYPE_CRAM);
		
		nQIndex++;
	}

	if (nScanline >= 240)
		return;

	CheckIRQ();

	if (255 == nLineCount)
	{
		if (nQIndex != nNrOfSR)
		{
			BYTE page;

			nLineCount = QueueSR[nQIndex];

			page = QueueVR[nQIndex] & 15;	// 2K
			SetVROM_Bank(0, EVRAM + page * 0x0800 + 0x0000, BANKTYPE_CRAM);
			SetVROM_Bank(1, EVRAM + page * 0x0800 + 0x0400, BANKTYPE_CRAM);
			
			page = (QueueVR[nQIndex] >> 4) & 15;	// 2K
			SetVROM_Bank(2, EVRAM + page * 0x0800 + 0x0000, BANKTYPE_CRAM);
			SetVROM_Bank(3, EVRAM + page * 0x0800 + 0x0400, BANKTYPE_CRAM);

			nQIndex++;
		}
	}
	else if (bSplitMode || bEnableIRQ)
	{
		nLineCount++;
	}
}

/*******************************************************************************
                           E N D  O F  F I L E
*******************************************************************************/
